package org.sample.jdbc.commons.bean;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.*;

/**
 * 使用jdk自带内省操作Bean
 * Created by Alan.ye on 2016/9/7.
 */
public class BeanUtils {

    private static Cache<Class<?>, Map<String, PropertyMethod>> beanInfoCache = new Cache(64);

    private static final List<String> ignores = new ArrayList();

    static {
        ignores.add("class");
        ignores.add("hibernateLazyInitializer");
        ignores.add("handler");
        ignores.add("fieldHandler");
    }

    public static <T> T copyBean(Object source, T target) {
        Map<String, PropertyMethod> targetBeanInfo = getBeanInfo(target.getClass());
        Map<String, PropertyMethod> sourceBeanInfo = getBeanInfo(source.getClass());

        for (Map.Entry<String, PropertyMethod> entry : targetBeanInfo.entrySet()) {
            String name = entry.getKey();
            PropertyMethod sourceMethod = sourceBeanInfo.get(name);
            if (sourceMethod != null) {
                Object value = getValue(source, sourceMethod.ReadMethod);
                setValue(target, entry.getValue().WriteMethod, value);
            }
        }
        return target;
    }

    public static <T> T copyBean(Object source, T target, String ignore) {
        Map<String, PropertyMethod> targetBeanInfo = getBeanInfo(target.getClass());
        Map<String, PropertyMethod> sourceBeanInfo = getBeanInfo(source.getClass());

        for (Map.Entry<String, PropertyMethod> entry : targetBeanInfo.entrySet()) {
            String name = entry.getKey();
            if (ignore.contains(name))
                continue;
            PropertyMethod sourceMethod = sourceBeanInfo.get(name);
            if (sourceMethod != null) {
                Object value = getValue(source, sourceMethod.ReadMethod);
                setValue(target, entry.getValue().WriteMethod, value);
            }
        }
        return target;
    }


    public static <T> T copyBeanNotNull(Object source, T target) {
        Map<String, PropertyMethod> targetBeanInfo = getBeanInfo(target.getClass());
        Map<String, PropertyMethod> sourceBeanInfo = getBeanInfo(source.getClass());

        for (Map.Entry<String, PropertyMethod> entry : targetBeanInfo.entrySet()) {
            String name = entry.getKey();
            PropertyMethod sourceMethod = sourceBeanInfo.get(name);
            if (sourceMethod != null) {
                Object value = getValue(source, sourceMethod.ReadMethod);
                if (value != null)
                    setValue(target, entry.getValue().WriteMethod, value);
            }
        }
        return target;
    }

    public static void cleanEmpty(Object target) {
        Map<String, PropertyMethod> beanInfo = getBeanInfo(target.getClass());
        for (Map.Entry<String, PropertyMethod> entry : beanInfo.entrySet()) {
            PropertyMethod propertyMethod = entry.getValue();
            Object value = getValue(target, propertyMethod.ReadMethod);
            if ("".equals(value))
                setValue(target, propertyMethod.WriteMethod, null);
        }
    }

    public static Map<String, Object> toMap(Object target) {
        Map<String, PropertyMethod> beanInfo = getBeanInfo(target.getClass());
        Map<String, Object> result = new HashMap(beanInfo.size());

        for (Map.Entry<String, PropertyMethod> entry : beanInfo.entrySet()) {
            Object value = getValue(target, entry.getValue().ReadMethod);
            result.put(entry.getKey(), value);
        }
        return result;
    }

    public static Map<String, Object> toMapNotNull(Object target) {
        Map<String, PropertyMethod> beanInfo = getBeanInfo(target.getClass());
        Map<String, Object> result = new HashMap(beanInfo.size());

        for (Map.Entry<String, PropertyMethod> entry : beanInfo.entrySet()) {
            Object value = getValue(target, entry.getValue().ReadMethod);
            if (value != null)
                result.put(entry.getKey(), value);
        }
        return result;
    }

    public static Object getValue(Object target, String name) {
        if (target == null)
            return null;
        return getValue(target, getReadMethod(target.getClass(), name));
    }

    public static Object getValue(Object obj, Method getter) {
        try {
            return getter.invoke(obj);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static void setValue(Object target, String name, Object value) {
        if (target == null)
            return;
        setValue(target, getWriteMethod(target.getClass(), name), value);
    }

    public static void setValue(Object obj, Method setter, Object value) {
        if (setter != null)
            if (value == null || setter.getParameterTypes()[0].isAssignableFrom(value.getClass()))
                try {
                    setter.invoke(obj, value);
                } catch (Exception e) {
                    e.printStackTrace();
                }
    }

    public static Method getReadMethod(Class<?> clazz, String propertyName) {
        return getBeanInfo(clazz).get(propertyName).ReadMethod;
    }

    public static Method getWriteMethod(Class<?> clazz, String propertyName) {
        return getBeanInfo(clazz).get(propertyName).WriteMethod;
    }

    public static Map<String, PropertyMethod> getPropertyMethod(Class<?> clazz) {
        return Collections.unmodifiableMap(getBeanInfo(clazz));
    }

    private static Map<String, PropertyMethod> getBeanInfo(Class<?> clazz) {

        return beanInfoCache.get(clazz, () -> {
            BeanInfo beanInfo = Introspector.getBeanInfo(clazz);

            PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
            Map<String, PropertyMethod> result = new HashMap(pds.length);

            for (PropertyDescriptor pd : pds) {
                String name = pd.getName();
                if (!ignores.contains(name))
                    result.put(name, new PropertyMethod(pd.getReadMethod(), pd.getWriteMethod()));
            }

            Introspector.flushFromCaches(clazz);
            return result;
        });
    }

    public static <T> T newInstance(Class<T> target) {
        try {
            return target.newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 获得泛型属性的泛型类型
     *
     * @param rootClass 根对象
     * @param fieldName 属性名
     * @return
     */
    public static Class getGenericType(Class rootClass, String fieldName) {
        try {
            Field field = rootClass.getDeclaredField(fieldName);
            ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
            return (Class) parameterizedType.getActualTypeArguments()[0];
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        }
    }

    public static class PropertyMethod {
        protected final Method ReadMethod;
        protected final Method WriteMethod;

        public PropertyMethod(Method readMethod, Method writeMethod) {
            this.ReadMethod = readMethod;
            this.WriteMethod = writeMethod;
        }

        public Method getReadMethod() {
            return ReadMethod;
        }

        public Method getWriteMethod() {
            return WriteMethod;
        }
    }
}